package body Ada_Builder is

   procedure Define_New_Scalar (
      this : in out Ada_Builder_Type;
      Name : String;
      Base : String := "";
      Min  : String := "";
      Max  : String := ""
      )
   is
   begin
      this.Append("type " & Name & " is ");
      if Base'Length > 0 then
         this.Append("new " & Base);
      end if;
      if Min'Length > 0 and Max'Length > 0 then
         this.Append(" range " & Min & " .. " & Max);
      end if;
      this.End_Define;
   end Define_New_Scalar;

   procedure Define_Sub_Scalar (
      this : in out Ada_Builder_Type;
      Name : String;
      Base : String;
      Min  : String := "";
      Max  : String := ""
      )
   is
   begin
      this.Append("subtype " & Name & " is " & Base);
      if Min'Length > 0 and Max'Length > 0 then
         this.Append(" range " & Min & " .. " & Max);
      end if;
      this.End_Define;
   end Define_Sub_Scalar;

   procedure Define_Private (
      this : in out Ada_Builder_Type;
      Name : String;
      Disc : String := "";
      Base : String := "";
      Limit : Boolean := False
      )
   is
   begin
      this.Append("type " & Name);
      if Disc'Length > 0 then
         this.Append("(" & Disc & ")");
      end if;
      this.Append(" is ");
      if Base'Length > 0 then
         this.Append("new " & Base & " with ");
      elsif Limit then
         this.Append("limited ");
      end if;
      this.Append("private");
      this.End_Define;
   end Define_Private;

   procedure Define_Var (
      this : in out Ada_Builder_Type;
      Name : String;
      Type_Name : String;
      Init_Value : String := ""
      )
   is
   begin
      this.Append(Name & " : " & Type_Name);
      if Init_Value'Length > 0 then
         this.Append(" := " & Init_Value);
      end if;
      this.Append(";");
      this.New_Line;
   end Define_Var;

   procedure Define_Param (
      this : in out Ada_Builder_Type;
      Name : String;
      Type_Name : String;
      Init_Value : String := ""
      )
   is
   begin
      this.F_Parameter_Names.Append(Name);
      this.F_Parameter_Types.Append(Type_Name);
      this.F_Parameter_Inits.Append(Init_Value);
   end Define_Param;

   procedure Print_Parameter_Defines (this : in out Ada_Builder_Type)
   is
   begin
      if not this.F_Parameter_Names.Is_Empty then
         this.Append(" (");
         this.Inc_Indent;
         this.New_Line;
         while not this.F_Parameter_Names.Is_Empty loop
            this.Append(this.F_Parameter_Names.First_Element);
            this.Append(" : ");
            this.Append(this.F_Parameter_Types.First_Element);
            declare
               Value : String := this.F_Parameter_Inits.First_Element;
            begin
               if Value'Length > 0 then
                  this.Append(" := " & Value);
               end if;
            end;
            this.F_Parameter_Names.Delete_First;
            this.F_Parameter_Types.Delete_First;
            this.F_Parameter_Inits.Delete_First;
            if not this.F_Parameter_Names.Is_Empty then
               this.End_Statement;
            end if;
            this.New_Line;
         end loop;
         this.Append(")");
      end if;
   end Print_Parameter_Defines;

   procedure Define_Proc (
      this : in out Ada_Builder_Type;
      Name : String;
      Spec : Boolean := True 
      )
   is
   begin
      this.Append("procedure " & Name); 
      Print_Parameter_Defines(this);
      if Spec then
         this.End_Define;
      else
         this.Begin_Is(Single_Line => True);
      end if;
   end Define_Proc;

   procedure Define_Entry (
      this : in out Ada_Builder_Type;
      Name : String;
      Spec : Boolean := True
      )
   is
   begin
      this.Append("entry " & Name);
      Print_Parameter_Defines(this);
      if Spec then
         this.End_Define;
      end if;
   end Define_Entry;
   
   procedure Define_Func (
      this        : in out Ada_Builder_Type;
      Name        : String;
      Result_Type : String;
      Spec        : Boolean := True 
      )
   is
   begin
      this.Append("function " & Name); 
      Print_Parameter_Defines(this);
      this.Append(" return " & Result_Type);
      if Spec then
         this.End_Define;
      else
         this.Begin_Is(Single_Line => True);
      end if;
   end Define_Func;

   procedure End_Define (this : in out Ada_Builder_Type)
   is
   begin
      this.End_Statement;
      this.New_Line;
   end End_Define;

   procedure Begin_Block (
      this : in out Ada_Builder_Type;
      Name : String;
      Kind : String
      )
   is
   begin
      this.Append(Kind & " " & Name);
      this.F_Blocks.Append(Name);
   end Begin_Block;

   procedure End_Block (this : in out Ada_Builder_Type)
   is
   begin
      if not this.F_Blocks.Is_Empty then
         this.Append("end " & this.F_Blocks.Last_Element & ";");
         this.Dec_Indent;
         this.New_Line;
      end if;
   end End_Block;

   procedure Begin_Is (
      this : in out Ada_Builder_Type;
      Single_Line : Boolean := False 
      )
   is
   begin
      if Single_Line then
         this.New_Line;
      else
         this.Append(" ");
      end if;
      this.Append("is");
      this.Inc_Indent;
      this.New_Line;
   end Begin_Is;

   procedure Begin_Decl (this : in out Ada_Builder_Type)
   is
   begin
      this.Append("declare");
      this.Inc_Indent;
      this.New_Line;
   end Begin_Decl;

   procedure Begin_Do (this : in out Ada_Builder_Type)
   is
   begin
      this.Append("do");
      this.Inc_Indent;
      this.New_Line;
   end Begin_Do;

   procedure Begin_Begin (this : in out Ada_Builder_Type)
   is
   begin
      this.End_Indent;
      this.Append("begin");
      this.Inc_Indent;
      this.New_Line;
   end Begin_Begin;

   procedure Begin_If (
      this : in out Ada_Builder_Type;
      Expr : String
      )
   is
   begin
      this.Append("if " & Expr & " then");
      this.Inc_Indent;
      this.New_Line;
   end Begin_If;

   procedure Begin_Elsif (
      this : in out Ada_Builder_Type;
      Expr : String
      )
   is
   begin
      this.Dec_Indent;
      this.New_Line;
      this.Append("elsif " & Expr & " then");
      this.Inc_Indent;
      this.New_Line;
   end Begin_Elsif;

   procedure Begin_Else (this : in out Ada_Builder_Type)
   is
   begin
      this.Dec_Indent;
      this.New_Line;
      this.Append("else");
      this.Inc_Indent;
      this.New_Line;
   end Begin_Else;

   procedure End_If (this : in out Ada_Builder_Type)
   is
   begin
      this.Dec_Indent;
      this.New_Line;
      this.Append("end if;");
      this.New_Line;
   end End_If;

   procedure End_Statement (this : in out Ada_Builder_Type)
   is
   begin
      this.Append(";");
   end End_Statement;

   procedure Begin_Loop (this : in out Ada_Builder_Type)
   is
   begin
      this.Append("loop");
      this.Inc_Indent;
      this.New_Line;
   end Begin_Loop;

   procedure End_Loop (this : in out Ada_Builder_Type)
   is
   begin
      this.Dec_Indent;
      this.New_Line;
      this.Append("end loop;");
      this.New_Line;
   end End_Loop;

   procedure Begin_For_Loop (
      this : in out Ada_Builder_Type;
      I : String;
      R : String
      )
   is
   begin
      this.Append("for " & I & " in " & R & " ");
      this.Begin_Loop;
   end Begin_For_Loop;

   procedure Begin_Foreach_Loop (
      this : in out Ada_Builder_Type;
      I : String;
      R : String
      )
   is
   begin
      this.Append("for " & I & " of " & R & " ");
      this.Begin_Loop;
   end Begin_Foreach_Loop;

   procedure Begin_While_Loop (
      this : in out Ada_Builder_Type;
      Expr : String
      )
   is
   begin
      this.Append("while " & Expr & " ");
      this.Begin_Loop;
   end Begin_While_Loop;

   procedure Exit_Loop (
      this      : in out Ada_Builder_Type;
      Loop_Name : String := "";
      When_Expr : String := ""
      )
   is
   begin
      this.Append("exit loop");
      if Loop_Name'Length > 0 then
         this.Append(" " & Loop_Name);
      end if;
      if When_Expr'Length /= 0 then
         this.Append(" when " & When_Expr);
      end if;
      this.End_Statement;
   end Exit_Loop;

   procedure Push_Parameter (
      this : in out Ada_Builder_Type;
      Expr : String;
      Name : String := ""
      )
   is
      S : String := (if Name'Length > 0 then Name & " => " & Expr else Expr);
   begin
      this.F_Parameter_Stack.Append(S);
   end Push_Parameter;

   procedure Print_Parameter_Stack (this : in out Ada_Builder_Type)
   is
   begin
      case this.F_Parameter_Stack.Length is
         when 0 => null;
         when 1 => 
            this.Append("(" & this.F_Parameter_Stack.First_Element & ")");
            this.F_Parameter_Stack.Delete_First;
         when 2 .. 3 =>
            this.Append("(" & this.F_Parameter_Stack.First_Element);
            this.F_Parameter_Stack.Delete_First;
            while not this.F_Parameter_Stack.Is_Empty loop
               this.Append(", " & this.F_Parameter_Stack.First_Element);
               this.F_Parameter_Stack.Delete_First;
            end loop;
            this.Append(")");
         when others =>
            this.Append("(");
            this.Inc_Indent;
            this.New_Line;
            this.Append(this.F_Parameter_Stack.First_Element);
            this.F_Parameter_Stack.Delete_First;
            while not this.F_Parameter_Stack.Is_Empty loop
               this.Append(",");
               this.New_Line;
               this.Append(this.F_Parameter_Stack.First_Element);
               this.F_Parameter_Stack.Delete_First;
            end loop;
            this.Append(")");
      end case;
   end Print_Parameter_Stack;

   procedure Call_Proc (
      this : in out Ada_Builder_Type;
      Name : String
      )
   is
      multiline : Boolean := this.F_Parameter_Stack.Length > 3;
   begin
      this.Append(Name);
      Print_Parameter_Stack(this);
      this.End_Statement;
      if multiline then
         this.End_Indent;
      else
         this.New_Line;
      end if;
   end Call_Proc;

   procedure Calc_Func (
      this : in out Ada_Builder_Type;
      Name : String;
      Assign_To : String := ""
      )
   is
   begin
      if Assign_To'Length > 0 then
         this.Append(Assign_To & " := ");
      end if;
      this.Append(Name);
      Print_Parameter_Stack(this);
      if Assign_To'Length > 0 then
         this.End_Statement;
      end if;
   end Calc_Func;

   procedure Comment (
      this : in out Ada_Builder_Type;
      Line : String
      )
   is
   begin
      this.Append("-- " & Line);
      this.New_Line;
   end Comment;

   procedure Return_Expr (
      this   : in out Ada_Builder_Type;
      Expr   : String := "";
      Simple : Boolean := True
      )
   is
   begin
      this.Append("return");
      if Expr'Length = 0 then
         this.End_Statement;
      else
         this.Append(" " & Expr);
         if Simple then
            this.End_Statement;
         else
            this.Begin_Do;
         end if;
      end if;
   end Return_Expr;

   procedure End_Return (this : in out Ada_Builder_Type)
   is
   begin
      this.End_Indent;
      this.Append("end return;");
   end End_Return;

   procedure End_Indent (this : in out Ada_Builder_Type)
   is
   begin
      this.Dec_Indent;
      this.New_Line;
   end End_Indent;

   procedure Begin_Task_Select (this : in out Ada_Builder_Type)
   is
   begin
      this.Append("select");
      this.Inc_Indent;
      this.New_Line;
   end Begin_Task_Select;

   procedure End_Task_Select (this : in out Ada_Builder_Type)
   is
   begin
      this.End_Indent;
      this.Append("end select");
      this.End_Statement;
      this.New_Line;
   end End_Task_Select;

   procedure Begin_Task_Or (this : in out Ada_Builder_Type)
   is
   begin
      this.End_Indent;
      this.Append("or");
      this.Inc_Indent;
      this.New_Line;
   end Begin_Task_Or;

   procedure Begin_Task_Then_Abort (this : in out Ada_Builder_Type)
   is
   begin
      this.End_Indent;
      this.Append("then abort");
      this.Inc_Indent;
      this.New_Line;
   end Begin_Task_Then_Abort;

   procedure Begin_Task_Else (this : in out Ada_Builder_Type)
   is
   begin
      this.Begin_Else;
   end Begin_Task_Else;

   procedure Append_Statement (
      this : in out Ada_Builder_Type;
      Line : String
      )
   is
   begin
      this.Append(Line);
      this.End_Statement;
   end Append_Statement;


end Ada_Builder;
